////////////////////////////////
//usersys.lua
//Performs operations on users.
//DEFINES
////////////////////////////////

//This function is run on startup, to ensure that everything starts up properly.
function bbb.usersysinit()
	local steamid,data
	//If the server had a normal shutdown, then this folder should be empty.
	//If it crashed, it would have left over flat files.
	//The entire purpose of the flat files is to back up in case of crash.
	for k,v in ipairs(file.Find("bbb/users/*")) do
		steamid=string.gsub(string.upper(string.sub(v,1,-5)),"D",":")
		data=bbb.usergetdata(steamid) //Retrieve most current player info
		if(data!=false) then //Successful retrival
			bbb.userupload(steamid,data) //Upload them away.
		end
		file.Delete("bbb/users/"..v) //Erase their local file because they may no longer be active. They will be added soon if not.
	end
	hook.Add("PlayerInitialSpawn","bbb.userinitspawn",bbb.userinitspawn)
	hook.Add("PlayerDisconnected","bbb.userdisconnect",bbb.userdisconnect)
	timer.Create("bbb.usercheck",1,0,bbb.usercheck)
	bbb.usertimer=true
end

function bbb.userinitspawn(ply)
	if(ply:IsPlayer()==false) then
		bbb.error("USER INITAL SPAWN","Invalid player given.")
		return false
	end
	local steamid=ply:SteamID()
	print("Spawn: "..steamid)
	if(bbb.userisbanned(steamid)==true) then //If the player is banned then...
		print("Should kick user, why not?")
		game.ConsoleCommand("kickid "..ply:UserID().." \"Player is banned.\"\n")
	end
	local data=bbb.usergetdata(steamid)
	if(data!=false) then
		bbb.users[steamid]=data
	end
end

function bbb.userdisconnect(ply)
	if(ply:IsPlayer()==false) then
		bbb.error("USER DISCONNECT","Invalid player given.")
		return false
	end
	local steamid=ply:SteamID()
	print("Disconnect: "..steamid)
	if(bbb.users[steamid]!=nil) then //They might not be initalized yet, so just in case...
		if(bbb.userdatatosql(ply)!=false) then
			local fname=string.gsub(steamid,":","d")..".txt"
			file.Write("bbb/userbackup/"..fname,file.Read("bbb/users/"..fname)) //Make a backup of their stuff.
			file.Delete("bbb/users/"..fname) //Erase their local file because it seems to be a valid disconnect
		end
	end
	bbb.users[steamid]=nil //No longer in the local table, if userdisconnect is called they shouldn't be in the table.
	//If that is actually an error then they will be readded soon.
end

//This is a debug function, and shouldn't actually be called.
function bbb.userdisconnectall()
	for k,v in ipairs(player.GetAll()) do
		bbb.userdisconnect(v)
	end
	timer.Destroy("bbb.usercheck")
end

function bbb.usercheck()
	//The usertimer prevents it from spamming a repeated timer error
	if(bbb.usertimer==false) then //If it didn't make it to the end of the function there was an error
		bbb.error("USER CHECK","Timer error, stopping timer.")
		timer.Destroy("bbb.usercheck") //Kill the timer
	end
	bbb.usertimer=false //It assumes there was an error
	local curnum=bbb.get("curusercheck") //Get the current user check.
	local totable,tofile,tosql
	totable=curnum%bbb.get("g1totable")
	tofile=curnum%bbb.get("g1tofile")
	tosql=curnum%bbb.get("g1tosql")
	for k,v in ipairs(player.GetAll()) do
		if(bbb.users[v:SteamID()]==nil) then
			bbb.userinitspawn(v) //They aren't in the system, initspawn them
		end
		if(totable==0) then //It should upload to a table
			bbb.userdatatotable(v)
		end
		if(tofile==0) then //It should upload to file
			bbb.userdatatofile(v)
		end
		if(tosql==0) then //It should upload to sql
			bbb.userdatatosql(v)
		end
	end
	bbb.set("g1curusercheck",(curnum+1)%bbb.get("g1tosql"))
	bbb.usertimer=true //If there was no error the value is reset
end

function bbb.userupload(steamid,data)
	//Just in case. You don't want to lose this stuff because it is important.
	file.Write("bbb/fakesql/"..string.gsub(steamid,":","d")..".txt",bbb.tabletostring(data) or "")
	local query
	local query2=""
	steamid=string.upper(steamid)
	data.steamid=steamid
	local duptable=table.Copy(data) //The duplication erases things that shouldn't be there.
	duptable.prevfrags=nil
	duptable.prevdeaths=nil
	bbb.upload("users",duptable,"steamid")
	return true
end

function bbb.userdetectdata(ply,current) //Return a table with user info, for this player, from the server.
	if not(ply:IsPlayer()) then //Not a player...
		bbb.error("USER DATA RETRIVAL","Input is not a player.")
		return false
	end
	current=current or {} //Protect from missing information
	local out={}
	out.name=ply:GetName()
	out.deaths=(current.deaths or 0)+(ply:Deaths()-(current.prevdeaths or 0))
	out.frags=(current.frags or 0)+(ply:Frags()-(current.prevfrags or 0))
	out.prevdeaths=out.deaths
	out.prevfrags=out.frags
	out.steamid=ply:SteamID()
	out.ip=ply:IPAddress()
	out.permissions=(current.permissions or bbb.USER)
	//The below code will total up the time.
	out.totaltime=(current.totaltime or 0)+((os.time()-(current.curtime or os.time()))/60)
	out.curtime=os.time() //This will be used to find the most recent.
	if(out.ip=="loopback") then
		out.ip="127.0.0.1" //Just to prettify things.
	end
	return out
end

function bbb.userhaspermissions(ply,permissions)
	if(ply:IsPlayer()!=true) then
		bbb.error("USER HAS PERMISSIONS","Invalid player.")
		return false
	end
	local steamid=ply:SteamID()
	if(bbb.users[steamid]==nil) then
		bbb.error("USER HAS PERMISSIONS","Player not in table.")
		return false
	end
	if(type(permissions)!="number") then
		bbb.error("USER HAS PERMISSIONS","Invalid input.")
		return false
	end
	permissions=bbb.uncompress(permissions) or {}
	local playerpermissions=bbb.uncompress(bbb.users[steamid].permissions) or {}
	for k,v in ipairs(permissions) do
		if(v==true and playerpermissions[k]!=true) then
			return false
		end
	end
	return true
end

function bbb.userdatatotable(ply)
	if(ply:IsPlayer()==false) then
		bbb.error("USER DATA TO TABLE","Invalid player given.")
		return false
	end
	local data
	data=bbb.userdetectdata(ply,bbb.users[ply:SteamID()]) //Retrieve player info
	bbb.users[ply:SteamID()]=data //Save it.
	return true
end

function bbb.userdatatofile(ply)
	if(ply:IsPlayer()==false) then
		bbb.error("USER DATA TO FILE","Invalid player given.")
		return false
	end
	local data,ufile
	data=bbb.userdetectdata(ply,bbb.users[ply:SteamID()]) //Retrieve player info
	ufile=bbb.tabletostring(data) or "" //Protect against errors
	file.Write("bbb/users/"..string.gsub(ply:SteamID(),":","d")..".txt",ufile)
	return true
end

//This function actually goes to a file, as a place holder until sql is implemented
function bbb.userdatatosql(ply)
	if(ply:IsPlayer()==false) then
		bbb.error("USER DATA TO SQL","Invalid player given.")
		return false
	end
	local data,steamid
	steamid=ply:SteamID()
	data=bbb.userdetectdata(ply,bbb.users[steamid]) //Retrieve player info
	if(bbb.userupload(steamid,data)==true) then
		return true
	end
	return false
end

function bbb.usergetdata(steamid) //This will attempt to find the most current user data we have. Access sql database, use with care.
	local tabletime=0
	local filetime=0
	local sqltime=0
	if(bbb.users[steamid]!=nil) then //If we have them stored locally...
		tabletime=bbb.users[steamid].curtime or 0
	end
	//Check from file
	local filedata={}
	filedata=bbb.usergetfromfile(steamid) or {curtime=0}
	filetime=filedata.curtime or 0
	
	//Check from sql
	local sqldata={}
	sqldata=bbb.usergetfromsql(steamid) or {curtime=0}
	sqltime=sqldata.curtime or 0
	
	local maxtime=math.max(tabletime,filetime,sqltime) //Get the most recent one.
	if(maxtime==0) then //There are no records on them. Time to see if they are currently in the server, so we can give them new records.
		for k,v in ipairs(player.GetAll()) do //Iterate through players and see if they are active.
			if(v:SteamID()==steamid) then
				return bbb.userdetectdata(v)
			end
		end
		return false //NOTHING!
	end
	
	if(maxtime==tabletime) then //Return info from table.
		return bbb.users[steamid]
	end
	if(maxtime==filetime) then //From file...
		return filedata //It was retrieved from the file earlier. It would not be the max if it didn't exist.
	end
	if(maxtime==sqltime) then //From sql.
		return sqldata //Nab from sql.
	end
	return false
end

//Again, this actually gets information from a file, until sql is implemented
function bbb.usergetfromfile(steamid)
	local datfile="bbb/users/"..string.gsub(steamid,":","d")..".txt";
	if(file.Exists(datfile)) then
		local datstring=file.Read(datfile)
		datfile=bbb.stringtotable(datstring) or {}
		return datfile //This is actually the user data. Oh well.
	end
	return false //Nothing is there.
end

function bbb.usergetfromsql(steamid)
	local data=bbb.query("SELECT * FROM users WHERE steamid='"..steamid.."';")
	local order=bbb.query("DESCRIBE users") //I hate to do this, but what if the ordering changes for some reason?
	
	if(type(data)=="table" and type(order)=="table") then //Valid queries...
		if(#data!=0) then //There are no results for that person. Revert to fallback.
			//The sql returns the information in the same order that it is in the table. (I hope)
			local output={}
			local num
			for key,info in ipairs(order) do //Grab the information from the columns
				output[info[1]]=data[1][key]
				num=tonumber(output[info[1]]) //If it is numerical, fix it.
				if(num!=nil) then
					output[info[1]]=num
				end
			end
			return output
		end
	end
	//Fallback in case of failure.
	local datfile="bbb/fakesql/"..string.gsub(steamid,":","d")..".txt"; //The gsub pulls out the :, which is invalid in files.
	if(file.Exists(datfile)) then
		local datstring=file.Read(datfile)
		return bbb.stringtotable(datstring) or {}
	end
	return false //Nothing is there.
end

//This section deals with bans.
function bbb.userban(steamid,length)
	local unbantime=os.time()+length
	if(length==0) then
		unbantime=0 //Forever.
	end
	if(bbb.users[steamid]!=nil) then //Locally stored, ban them here.
		bbb.users[steamid].banned=unbantime //They will be unbanned if the time is greater than this.
	end
	//This updates the sql to fix everything.
	bbb.upload("users",{steamid=steamid,banned=unbantime},"steamid")
end

function bbb.userisbanned(steamid)
	local unbantime=bbb.query("SELECT banned FROM users WHERE steamid='"..bbb.escape(string.upper(steamid)).."'")
	if(type(unbantime)=="table") then
		if(unbantime[1]!=nil and unbantime[1][1]!=nil) then
			unbantime=tonumber(unbantime[1][1]) //The [1][1] gets the number from the query results.
			if(unbantime==nil) then //Who knows what went wrong.
				return false
			end
			if(os.time()<=unbantime or unbantime==0) then
				return true //Only if they are definitely banned to we return true
			end
		end
	end
	return false
end

/* Currently useless, but possibly useful later.
function bbb.usergetvalue(steamid,value)
	if(type(steamid)!="string" or type(value)!="string") then
		bbb.error("USER VALUE RETRIVAL","Invalid inputs.")
		return false
	end
	if(bbb.users[steamid]==nil) then
		bbb.error("USER VALUE RETRIVAL","'"..tostring(steamid).."' is not in the user table.")
		return false
	end
	if(bbb.users[steamid][value]==nil) then
		bbb.error("USER VALUE RETRIVAL","'"..tostring(value).."' is not in the user's table.")
		return false
	end
	return bbb.users[steamid][value]
end
function bbb.usersetvalue(steamid,value,input,append)
	if(type(steamid)!="string" or type(value)!="string") then
		bbb.error("USER VALUE SETTING","Invalid inputs.")
		return false
	end
	if(bbb.users[steamid]==nil) then
		bbb.error("USER VALUE SETTING","'"..tostring(steamid).."' is not in the user table.")
		return false
	end
	if(append!=true) then //If not appending, erase prior data
		bbb.users[steamid][value]=nil
	end
	if(type(input)=="string") then //If string add stuff to the end
		bbb.users[steamid][value]=(bbb.users[steamid][value] or "")..(input or "")
	end
	if(type(input)=="number") then //If number add
		bbb.users[steamid][value]=(bbb.users[steamid][value] or 0)+(input or 0)
	end
end
*/
